package com.orbbec.orbbecsdkexamples.activity;

import android.content.Context;
import android.graphics.Bitmap;
import android.os.Bundle;
import android.text.TextUtils;
import android.util.Log;
import android.view.View;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;

import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import com.orbbec.obsensor.Config;
import com.orbbec.obsensor.DepthFrame;
import com.orbbec.obsensor.Device;
import com.orbbec.obsensor.DeviceChangedCallback;
import com.orbbec.obsensor.DeviceInfo;
import com.orbbec.obsensor.DeviceList;
import com.orbbec.obsensor.FrameSet;
import com.orbbec.obsensor.LobClass;
import com.orbbec.obsensor.OBContext;
import com.orbbec.obsensor.Pipeline;
import com.orbbec.obsensor.Sensor;
import com.orbbec.obsensor.SensorType;
import com.orbbec.obsensor.StreamType;
import com.orbbec.obsensor.VideoStreamProfile;
import com.orbbec.orbbecsdkexamples.R;
import com.orbbec.orbbecsdkexamples.utils.FrameConverter;
import com.orbbec.orbbecsdkexamples.utils.LocalUtils;
import com.orbbec.orbbecsdkexamples.view.OBGLView;

import org.opencv.android.BaseLoaderCallback;
import org.opencv.android.LoaderCallbackInterface;
import org.opencv.android.OpenCVLoader;
import org.opencv.android.Utils;
import org.opencv.core.Mat;

import java.util.Locale;
import java.util.Map;

import static com.orbbec.obsensor.DeviceProperty.OB_PROP_BRT_BOOL;
import static com.orbbec.obsensor.DeviceProperty.OB_PROP_DEPTH_POSTFILTER_BOOL;
import static com.orbbec.obsensor.DeviceProperty.OB_PROP_DISPARITY_TO_DEPTH_BOOL;
import static com.orbbec.obsensor.DeviceProperty.OB_PROP_MAX_DEPTH_INT;
import static com.orbbec.obsensor.DeviceProperty.OB_PROP_MIN_DEPTH_INT;

/**
 * Depth Viewer
 */
public class DepthViewerActivity extends BaseActivity {
    private static final String TAG = "DepthViewerActivity";

    private Pipeline mPipeline;

    private Thread mStreamThread;
    private volatile boolean mIsStreamRunning;

    private OBGLView mDepthView;
    private ImageView imageView;
    private Device mDevice;

    private DeviceChangedCallback mDeviceChangedCallback = new DeviceChangedCallback() {
        @Override
        public void onDeviceAttach(DeviceList deviceList) {
            try {
//                onDeviceDetach(deviceList);
                if (null == mPipeline) {
                    // 2.Create Device and initialize Pipeline through Device
                    mDevice = deviceList.getDevice(0);

                    Sensor depthSensor = mDevice.getSensor(SensorType.DEPTH);
                    if (null == depthSensor) {
                        showToast(getString(R.string.device_not_support_depth));
                        mDevice.close();
                        mDevice = null;
                        return;
                    }
                    //__add by yzd
                    mDevice.setPropertyValueI(OB_PROP_MIN_DEPTH_INT, 300);
                    mDevice.setPropertyValueI(OB_PROP_MAX_DEPTH_INT, 1500);
                    //mDevice.setPropertyValueI(OB_PROP_DEPTH_POSTFILTER_BOOL, 4);
                    //mDevice.setPropertyValueB(OB_PROP_DISPARITY_TO_DEPTH_BOOL, true);
                    //mDevice.setPropertyValueB(OB_PROP_BRT_BOOL, true);
                    Log.i("yzd", "depth set ok");
                    //__end
                    mPipeline = new Pipeline(mDevice);

                    // 3.Create Pipeline configuration
                    Config config = new Config();

                    // 4.Obtain the depth stream configuration and configure it to Config.
                    // Here, matching is based on the width and frame rate. The matching meets
                    // the configuration of a width of 640 and a frame rate of 30fps.
                    VideoStreamProfile streamProfile = getStreamProfile(mPipeline, SensorType.DEPTH);

                    // 5.Enable deep streaming configuration
                    if (null != streamProfile) {
                        printStreamProfile(streamProfile.as(StreamType.VIDEO));
                        config.enableStream(streamProfile);
                        streamProfile.close();
                    } else {
                        mPipeline.close();
                        mPipeline = null;
                        mDevice.close();
                        mDevice = null;

                        config.close();

                        Log.w(TAG, "No target stream profile!");
                        showToast(getString(R.string.init_stream_profile_failed));
                        return;
                    }

                    // 6.Start sensor stream
                    mPipeline.start(config);

                    // 7.release config
                    config.close();


                    // 8.Create a thread to obtain Pipeline data
                    start();
                }
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                // 9.Release device list resources
                deviceList.close();
            }
        }

        @Override
        public void onDeviceDetach(DeviceList deviceList) {
            try {
                if (mDevice != null) {
                    Log.d("TAG", "Device is detached");
                    for (int i = 0, N = deviceList.getDeviceCount(); i < N; i++) {
                        String uid = deviceList.getUid(i);
                        DeviceInfo deviceInfo = mDevice.getInfo();
                        if (null != deviceInfo && TextUtils.equals(uid, deviceInfo.getUid())) {
                            stop();
                            mPipeline.stop();
                            mPipeline.close();
                            mPipeline = null;
                            mDevice.close();
                            mDevice = null;
                        }
                        deviceInfo.close();
                    }
                }
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                try {
                    deviceList.close();
                } catch (Exception ignore) {
                }
            }
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        init(this);
        super.onCreate(savedInstanceState);
        setTitle("DepthViewer");
        setContentView(R.layout.activity_depth_viewer);
        mDepthView = findViewById(R.id.depthview_id);
        imageView = findViewById(R.id.iv_test);
    }
    public static void init(Context mContext) {
        BaseLoaderCallback mLoaderCallback = new BaseLoaderCallback(mContext) {
            @Override
            public void onManagerConnected(int status) {
                super.onManagerConnected(status);
                switch (status) {
                    case LoaderCallbackInterface.SUCCESS:
                        break;
                    default:
                        super.onManagerConnected(status);
                        break;
                }
            }
        };
        if (!OpenCVLoader.initDebug()) {
            Log.d("OpenCV", "Internal OpenCV library not found. Using OpenCV Manager for initialization");
            OpenCVLoader.initAsync(OpenCVLoader.OPENCV_VERSION, mContext, mLoaderCallback);
        } else {
            Log.d("OpenCV", "OpenCV library found inside package. Using it!");
            mLoaderCallback.onManagerConnected(LoaderCallbackInterface.SUCCESS);
        }
    }

    @Override
    protected void onStart() {
        super.onStart();
        initSDK();
    }

    @Override
    protected void onStop() {
        try {
            //Stop getting Pipeline data
            stop();

            // Stop the Pipeline and release
            if (null != mPipeline) {
                mPipeline.stop();
                mPipeline.close();
            }

            // Release Device
            if (mDevice != null) {
                mDevice.close();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }

        releaseSDK();
        super.onStop();
    }

    @Override
    protected DeviceChangedCallback getDeviceChangedCallback() {
        return mDeviceChangedCallback;
    }

    private void showToast(String msg) {
        runOnUiThread(() -> Toast.makeText(DepthViewerActivity.this, msg, Toast.LENGTH_SHORT).show());
    }

    private void start() {
        mIsStreamRunning = true;
        if (null == mStreamThread) {
            mStreamThread = new Thread(mStreamRunnable);
            mStreamThread.start();
        }
    }

    private void stop() {
        mIsStreamRunning = false;
        if (null != mStreamThread) {
            try {
                mStreamThread.join(300);
            } catch (InterruptedException e) {
            }
            mStreamThread = null;
        }
    }

    private void dumpDevices() {
        try (DeviceList deviceList = mOBContext.queryDevices()) {
            try {
                StringBuilder builder = new StringBuilder();
                // 2.Check the SDK version
                builder.append("SDK VersionName = " + OBContext.getVersionName() + "\n");
                builder.append("SDK StageVersion = " + OBContext.getStageVersion() + "\n");

                // 3.Get the number of devices
                int deviceCount = deviceList.getDeviceCount();
                if (deviceCount <= 0) {
                    builder.append("Not device found.");
                }

                for (int i = 0; i < deviceCount; ++i) {
                    // 4.Create device based on device index
                    Device device = deviceList.getDevice(i);

                    // 5.Get device information
                    DeviceInfo info = device.getInfo();

                    // 6.Get device version information
                    if (deviceCount > 1) {
                        if (i > 0) {
                            builder.append("\n");
                        }
                        builder.append("Device[" + i + "]: \n");
                    }
                    builder.append("Name: " + info.getName() + "\n");
                    builder.append("Vid: " + LocalUtils.formatHex04(info.getVid()) + "\n");
                    builder.append("Pid: " + LocalUtils.formatHex04(info.getPid()) + "\n");
                    builder.append("Uid: " + info.getUid() + "\n");
                    builder.append("SN: " + info.getSerialNumber() + "\n");
                    builder.append("connectType: " + info.getConnectionType() + "\n");
                    String firmwareVersion = info.getFirmwareVersion();
                    builder.append("FirmwareVersion: " + firmwareVersion + "\n");
                    builder.append(dumpExtensionInfo(info.getExtensionInfo()));

                    // 7.Iterate through the sensors of the current device
                    for (Sensor sensor : device.querySensors()) {
                        // 8.Query sensor type
                        builder.append("Sensor:    " + sensor.getType() + "\n");
                    }

                    // 8.Release device information
                    info.close();

                    // 9.Release device resources
                    device.close();
                } // for deviceList
                Log.d("TAG", builder.toString());

            } catch (Exception e) {
                e.printStackTrace();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private String dumpExtensionInfo(String extensionInfo) {
        StringBuilder builder = new StringBuilder();
        if (TextUtils.isEmpty(extensionInfo)) {
            builder.append("extensionInfo: ").append(extensionInfo).append("\n");
            return builder.toString();
        }

        builder.append("extensionInfo: ");
        try {
            JsonElement jsonParser = JsonParser.parseString(extensionInfo);
            JsonObject rootObject = jsonParser.getAsJsonObject();
            builder.append("\n");
            for (Map.Entry<String, JsonElement> e : rootObject.entrySet()) {
                if (TextUtils.equals("extensioninfo", e.getKey().toLowerCase(Locale.US))) {
                    for (Map.Entry<String, JsonElement> e2 : e.getValue().getAsJsonObject().entrySet()) {
                        builder.append("        ").append(e2.getKey()).append(": ").append(e2.getValue().toString()).append("\n");
                    }
                } else {
                    builder.append("    ").append(e.getKey()).append(": ").append(e.getValue().toString()).append("\n");
                }
            }
            Log.d("1234", builder.toString());
        } catch (Exception e) {
            e.printStackTrace();
            builder.append(extensionInfo);
        }
        return builder.toString();
    }

    private Runnable mStreamRunnable = () -> {
        while (mIsStreamRunning) {
            try {
                // Obtain the data set in blocking mode. If it cannot be obtained after waiting for 100ms, it will time out.
                FrameSet frameSet = mPipeline.waitForFrameSet(100);

                if (null == frameSet) {
                    continue;
                }

                // Get depth flow data
                DepthFrame frame = frameSet.getDepthFrame();
                double rate = 0;
                if (frame != null) {
                    try {
                        Mat mat = FrameConverter.frame2mat(frame);
                        rate = FrameConverter.calculateFillRate(mat);

                        Bitmap bitmap = Bitmap.createBitmap(frame.getWidth(), frame.getHeight(), Bitmap.Config.ARGB_8888);
                        Utils.matToBitmap(mat, bitmap);
                        Log.d("TAG", "bitmap获取到了,rate: " + rate);
//                        runOnUiThread(() -> {
//                            imageView.setImageBitmap(bitmap);
//                        });
                        Thread.sleep(500);
                    } catch (Exception e) {
                        e.printStackTrace();
                    }


                    // Get data and render
                    byte[] frameData = new byte[frame.getDataSize()];
                    frame.getData(frameData);
                    if (rate > 0.1){
                        mDepthView.update(frame.getWidth(), frame.getHeight(), StreamType.DEPTH, frame.getFormat(), frameData, frame.getValueScale());
                        runOnUiThread(() ->{
                            Log.d("TAG", "bitmap获取到了,显示深度视图");
                            mDepthView.setVisibility(View.VISIBLE);
                        });
                    } else {
                        runOnUiThread(() ->{
                            mDepthView.setVisibility(View.GONE);
                        });
                    }


                    // Release depth data frame
                    frame.close();
                }

                // 释放数据集
                frameSet.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    };
}